#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>


namespace mylog {
    // 日志落地基类
    class LogSink {
    public:
        using ptr = std::shared_ptr<LogSink>;
        // 日志输出接口, data为日志的起始地址,len为日志的长度
        virtual void log(const char* data, size_t len) = 0;
        virtual ~LogSink() {};
    };


    // 检查并创建文件及其路径
   std::ofstream checkAndCreateFile(const std::string& filePath, std::ios_base::openmode mode);


    // 落地方向：标准输出
    class StdOutSink : public LogSink {
    public:
        void log(const char* data, size_t len) override {std::cout.write(data, len);}
    };

    // 落地方向：指定文件
    class FileSink : public LogSink {
    public:
        // 创建文件并打开
        FileSink(const std::string& pathname)
            : _pathname(pathname),
              _ofs(checkAndCreateFile(pathname, std::ios::binary | std::ios::app)) {}
        void log(const char* data, size_t len) override {_ofs.write(data, len);}
    private:
        std::string _pathname;
        std::ofstream _ofs;
    };

    // 落地方向：滚动文件(这里按照文件大小进行滚动)
    class RollBySizeSink : public LogSink {
    public:
        // 创建文件并打开
        RollBySizeSink(const std::string& basename, size_t max_size)
            : _basename(basename), _max_size(max_size), _cur_size(0), _name_cout(0) {
            createNewFile();
        }

        void log(const char* data, size_t len) override;

    private:
        // 得到要生成的日志文件的名称
        // 通过基础文件名 + 时间组成 + 计数器 生成真正的文件名
        std::string GetFileName();
        void createNewFile();

    private:
        // 基础文件名
        std::string _basename;
        // 文件大小限制
        size_t _max_size;
        // 当前文件的大小
        size_t _cur_size;
        std::ofstream _ofs;
        // 文件名称计数器(防止一秒之内创建多个文件时，使用同一个名称)
        size_t _name_cout;
    };


    // 落地方向类的工厂类  (通过此工厂类实现对落地方向的可扩展性)
    class SinkFactory {
    public:
        template <class SinkType, class... Args>
        static LogSink::ptr create(Args&&... args) {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}
